// Array function pump, used by various apps.
// Copyright 2006 (C) Ralph Thomas

#ifndef MISSION_ARRAY_DATAPUMP_ADAPTER_H
#define MISSION_ARRAY_DATAPUMP_ADAPTER_H

#include <adobe/value.hpp>
#include <boost/type_traits/function_traits.hpp>
#include <boost/function.hpp>
#include <vector>
#include <adobe/array.hpp>
#include <adobe/name.hpp>
#include <adobe/algorithm.hpp>
#include <binder.hpp>
#include <sstream>

namespace app {
	//
	/// The fn namespace contains the implementation details for the array
	/// function adapter. 
	//
	namespace fn {
		typedef std::vector<adobe::value_t> value_list_t;
		
		template<typename OUT_TYPE>
		inline OUT_TYPE get_value(const adobe::value_t& v) { return v.get<OUT_TYPE>(); }
		template<>
		inline adobe::value_t get_value<adobe::value_t>(const adobe::value_t& v) { return v; }

		template<typename FUNCTION_TYPE, int ARITY>
		struct update {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				std::stringstream ss;
				ss << "No partial specialization for array functions with " << ARITY << " arguments -- add one to ui/array_datapump_adapter.h";
				throw std::runtime_error( ss.str() );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 1> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 2> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 3> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>::type>(values[2])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 4> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>::type>(values[2]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>::type>(values[3])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 5> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>::type>(values[2]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>::type>(values[3]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>::type>(values[4])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 6> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>::type>(values[2]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>::type>(values[3]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>::type>(values[4]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg6_type>::type>::type>(values[5])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 7> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>::type>(values[2]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>::type>(values[3]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>::type>(values[4]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg6_type>::type>::type>(values[5]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg7_type>::type>::type>(values[6])
				) );
			}
		};
		template<typename FUNCTION_TYPE>
		struct update<FUNCTION_TYPE, 8> {
			adobe::value_t operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				return adobe::value_t( func(
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>::type>(values[0]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>::type>(values[1]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>::type>(values[2]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>::type>(values[3]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>::type>(values[4]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg6_type>::type>::type>(values[5]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg7_type>::type>::type>(values[6]),
					get_value<typename boost::remove_const<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg8_type>::type>::type>(values[7])
				) );
			}
		};
	}
	template<typename FUNCTION_TYPE>
	class array_datapump_adapter_t : public adobe::binder_t {
		typedef std::vector<adobe::binder_helper_t*>	binder_list_t;
		typedef boost::function<FUNCTION_TYPE>		function_t;
		function_t					function_m;
		binder_list_t					binders_m;
		fn::value_list_t				values_m;
		adobe::value_t					last_value_m;
		adobe::sheet_t::monitor_value_t			value_monitor_m;
		adobe::array_t					source_m;
		adobe::sheet_t&					sheet_m;
		//
		/// Called when a value in the values array should change.
		//
		void update_array( unsigned int index, const adobe::value_t& value ) {
    		values_m[index] = value;
			update();
		}
		void build_helper( unsigned int& index, const adobe::value_t& value ) {
			adobe::binder_helper_t* binder = new adobe::binder_helper_t( sheet_m );
			binder->initialize( value, boost::bind( &array_datapump_adapter_t::update_array, this, index, _1 ), 0 );
			binders_m.push_back( binder );
			index++;
		}
	  public:
		array_datapump_adapter_t( adobe::sheet_t& sheet, const adobe::array_t& source_array )
		: source_m( source_array ), sheet_m( sheet ) {
			//
			// Verify that we have enough arguments for our function.
			//
			if( source_array.size() < (unsigned int)boost::function_traits<FUNCTION_TYPE>::arity )
				throw std::runtime_error( "insufficient arguments for function" );
			//
			// Create a binder helper for each item in the array.
			//
			values_m.resize( source_array.size() );
			unsigned int index( 0 );
			adobe::for_each( source_array,
				boost::bind( &array_datapump_adapter_t::build_helper, this, boost::ref( index ), _1 ) );
		}
        void set_function( const boost::function<FUNCTION_TYPE>& func ) {
            function_m = func;
        }
		virtual ~array_datapump_adapter_t() {
			for( binder_list_t::iterator i = binders_m.begin(); i != binders_m.end(); i++ )
				delete *i;
		}
		virtual void set( const adobe::value_t& val ) {
			//throw std::runtime_error( "this datapump is read only!" );
		}
		virtual void set_signals( const adobe::sheet_t::monitor_value_t& vm, const adobe::sheet_t::monitor_active_t& am ) {
			value_monitor_m = vm;
			if( am ) am( false );
			if( vm ) vm( last_value_m );
		}
		virtual void substitute( const adobe::name_t& name, const adobe::value_t& val ) {
			adobe::for_each( binders_m, boost::bind( &adobe::binder_helper_t::substitute, _1, name, val ) );
		}
		virtual adobe::binder_ptr_t copy() const {
            array_datapump_adapter_t<FUNCTION_TYPE>* ada = new array_datapump_adapter_t<FUNCTION_TYPE>( sheet_m, source_m );
            ada->set_function( function_m );
			return adobe::binder_ptr_t( ada );
		}
		virtual void invoke() {}
        void update() {
			if( value_monitor_m && function_m ) {
				fn::update<FUNCTION_TYPE, boost::function_traits<FUNCTION_TYPE>::arity> updater;
				last_value_m = updater( function_m, values_m );
				value_monitor_m( last_value_m );
			}
        }
		static adobe::binder_ptr_t create( const boost::function<FUNCTION_TYPE>& f, adobe::sheet_t& sheet, const adobe::array_t& source ) {
			array_datapump_adapter_t<FUNCTION_TYPE>* ada = new array_datapump_adapter_t<FUNCTION_TYPE>( sheet, source );
            ada->set_function(f);
            return adobe::binder_ptr_t(ada);
		}
	};
}

#endif

